Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add pagination in the stock_movement end-point #6960

Merged
merged 5 commits into from
Apr 1, 2023

Conversation

mbayopanda
Copy link
Collaborator

@mbayopanda mbayopanda commented Mar 6, 2023

This PR adds paging in the stock_movment end-point.

To perform queries with paging, you have to add: ?paging=true in the HTTP request, which returns an object with two elements:

{
  rows: [], // records
  pager: {
    "total": 18, // total of records found in the database
    "page": 4, // the current page, pages are indexed from 1
    "page_size": 5, // the number of records returned by pages
    "page_min": 15, // the minimum counted record of the page
    "page_max": 20, // the maximum counted record of the page
    "page_count": 4 // the total of pages
  } // pagination information
}

To test the feature:

  • Log into BHIMA (with browser or REST Tools)
  • Go to this route: stock/lots/movements/?paging=true to have pager information
  • Go to this route: stock/lots/movements/?paging=true&page=2 to have page 2 of records
  • Go to this route: stock/lots/movements/?paging=true&page=2&limit=10 to have page 2 when the number of records on each page must be 10

This feature is enabled for these end-points:

  • /stock/lots/depots/ : for getting the list of lots with their quantities (see Lots in stock registry)
  • /stock/lots/depotsDetailed/ : for getting the detailed list of lots (see Lots in stock registry)
  • /stock/lots/movements/ : for getting the list of stock movements made (see Stock movements registry)
  • /stock/inventories/depots/ : for getting the list of inventories without consideration of lots (see Article in stock registry)

@jmcameron
Copy link
Collaborator

Please add a description of how to test this PR.

@jniles
Copy link
Collaborator

jniles commented Mar 6, 2023

I'm also curious what this is going to be used for. It seems like it might be better to create it's own route. I think all API routes in BHIMA that support filtering currently return the maximum amount of records that meet a user's filter criteria, and this breaks that API contract.

@mbayopanda
Copy link
Collaborator Author

I'm also curious what this is going to be used for. It seems like it might be better to create it's own route. I think all API routes in BHIMA that support filtering currently return the maximum amount of records that meet a user's filter criteria, and this breaks that API contract.

@jniles this can be used for server-side pagination, which gives valuable information to the user instead of just loading a limited number of records to not break the client, the pagination can be applied to any list of records (grid, table, etc.) or through API call directly.

Actually, the BHIMA API is huge, so we cannot apply this change to all routes but we can do that progressively without breaking the current behavior of BHIMA routes.

Copy link
Collaborator

@jmcameron jmcameron left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code looks okay. I tried the suggested tests and they seem to work fine.

It is hard to evaluate the utility of this change until it is applied to a report or register page, so I do not have a firm opinion on whether this is a good idea or not. But I do not object to merging it since I think (guess) it will not affect any existing pages or reports.

@jmcameron
Copy link
Collaborator

@mbayopanda What do you want to do next on this PR? Do we need further discussion?

@jmcameron
Copy link
Collaborator

According to @mbayopanda (on WhatsApp): Currently there is the BAO System team for their collaboration in the SEMI project they would like to integrate the BHIMA data into their report; they have been using our current API and they have asked me if we can provide the data per page, which will allow not loading all the data at once but as needed. We can merge this PR as long as it doesn't break what's already there.

Copy link
Collaborator

@jmcameron jmcameron left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I looked at the code again and it seems fine.
I tested it with the implemented endpoints and they work nicely.

I had one question that you might consider: What should the REST queries return for pages beyond the end of the data? According to some best practices perhaps we should return a HTML 204 response. Currently a empty JSON "pager" object is returned. For instance, calling

http://localhost:8080/stock/lots/depots/?paging=true&limit=4&page=4

Produces

{"pager":{"total":18,"page":4,"page_size":4,"page_min":12, "page_max":16,"page_count":5},"rows":[]}

It is not clear that the receiving party will be able to interpret this correctly.
Notice that there is no indication that there are only 3 pages with data. Also, what does the "page_max" mean here? There are 11 items total. Perhaps we should discuss this with the BAO folks and see what response would work best for them.

@mbayopanda
Copy link
Collaborator Author

mbayopanda commented Mar 20, 2023

I looked at the code again and it seems fine. I tested it with the implemented endpoints and they work nicely.

I had one question that you might consider: What should the REST queries return for pages beyond the end of the data? According to some best practices perhaps we should return a HTML 204 response. Currently a empty JSON "pager" object is returned. For instance, calling

http://localhost:8080/stock/lots/depots/?paging=true&limit=4&page=4

Produces

{"pager":{"total":18,"page":4,"page_size":4,"page_min":12, "page_max":16,"page_count":5},"rows":[]}

It is not clear that the receiving party will be able to interpret this correctly. Notice that there is no indication that there are only 3 pages with data. Also, what does the "page_max" mean here? There are 11 items total. Perhaps we should discuss this with the BAO folks and see what response would work best for them.

@jmcameron this is a bug with the way we use SELECT COUNT(*) we have to use SELECT COUNT(DISTINCT specific_column) since the code is used for many context in this context the result is not correct;

A temporary solution is to use naive code and javascript to fix that.

@mbayopanda
Copy link
Collaborator Author

@jmcameron could you review this PR please

Copy link
Collaborator

@jmcameron jmcameron left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I reviewed the code again and made some suggestions for improving variable names, etc.

I tested the end points using bhima_test:

  • /stock/lots/depots/?paging=true worked as expected
  • /stock/lots/depotsDetailed/ failed with this message: {"status":500}
  • /stock/lots/movements/?paging=true worked as expected
  • /stock/inventories/depots/ worked as expected, but when I asked for a page beyond the limit, I got: [] which is a different response than the other endpoints.

Suggestions

  • All endpoints should return data in the same format
  • When asking for a page beyond the limit, some endpoints return:
    • {"rows":[],"pager":{"total":18,"page":3,"page_size":10,"page_min":20, "page_max":30,"page_count":2}}
    • I see that page_count has been corrected. Thank you.
    • However, I would prefer to see page_min and page_max to be null or something to indicate that they are not valid for this page beyond the end of the data. I'm happy to see that rows = [] in this case, but I think that it would be clearer to whoever is dealing with this data to know that the page_min and page_max values are invalid in this response.


// add the minimum delay to the rows
filteredRows.forEach(row => {
_filteredRows.forEach(row => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find using the _ prefix not very helpful for making the code easy to understand. Rather than _filteredRows, I suggest using filteredRowsPaged or filteredRowsPaging (or something similar) which is more descriptive and easy to understand why _filteredRows is different from filteredRows.

@@ -1169,18 +1173,21 @@ async function listLotsDepotDetailed(req, res, next) {
db.exec(sqlGetMonthlyStockMovements, [db.bid(params.depot_uuid), params.startDate, params.dateTo]),
]);

data.forEach(current => {
const _data = !params.paging ? data : data.rows;
const _dataPreviousMonth = !params.paging ? dataPreviousMonth : dataPreviousMonth.rows;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, I suggest calling this dataPreviousMonthPaged or something similar to improve code readability.

@@ -1169,18 +1173,21 @@ async function listLotsDepotDetailed(req, res, next) {
db.exec(sqlGetMonthlyStockMovements, [db.bid(params.depot_uuid), params.startDate, params.dateTo]),
]);

data.forEach(current => {
const _data = !params.paging ? data : data.rows;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest dataPaged

// FIXME: Performance issue, use SQL COUNT in a better way
const total = (await this.exec(filters.getAllResultQuery(sql.concat(' ', tables)), queryParameters)).length;
const page = params.page ? parseInt(params.page, 10) : 1;
const limit = params.limit ? parseInt(params.limit, 10) : 100;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest computing page_count here. And when computing page_min and page_max below, if page > page_count, set them to `null' since they are invalid for pages beyond the max. And apply similar fixes to the FilterParser below.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have to set page_min and page_max after the execution of the query since the page_min is sent to the SQL query in the OFFSET part.

@mbayopanda
Copy link
Collaborator Author

I reviewed the code again and made some suggestions for improving variable names, etc.

I tested the end points using bhima_test:

  • /stock/lots/depots/?paging=true worked as expected
  • /stock/lots/depotsDetailed/ failed with this message: {"status":500}
  • /stock/lots/movements/?paging=true worked as expected
  • /stock/inventories/depots/ worked as expected, but when I asked for a page beyond the limit, I got: [] which is a different response than the other endpoints.

Suggestions

  • All endpoints should return data in the same format

  • When asking for a page beyond the limit, some endpoints return:

    • {"rows":[],"pager":{"total":18,"page":3,"page_size":10,"page_min":20, "page_max":30,"page_count":2}}
    • I see that page_count has been corrected. Thank you.
    • However, I would prefer to see page_min and page_max to be null or something to indicate that they are not valid for this page beyond the end of the data. I'm happy to see that rows = [] in this case, but I think that it would be clearer to whoever is dealing with this data to know that the page_min and page_max values are invalid in this response.

Thank you @jmcameron for your review

@mbayopanda
Copy link
Collaborator Author

@jmcameron can I get a review, please?

About this /stock/lots/depotsDetailed/ failed with this message: {"status":500}, the end-point requires a depot_uuid in params, it's a wrong definition of a REST endpoint.

Copy link
Collaborator

@jmcameron jmcameron left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the code improvements.

I tested the endpoints again:

  • /stock/lots/depots/?paging=true worked as expected
  • /stock/lots/movements/?paging=true worked as expected
  • /stock/inventories/depots/?paging=true worked as expected
  • /stock/lots/depotsDetailed/ seems to be working(?)

Regarding depotsDetailed. When I use the URL below, I get no results, which I do not understand, since the UUID if for the primary depot. It is likely that I have the format or data of the URL incorrect.

  • /stock/lots/depotDetailed/?depot_uuid=F9CAEB16168443C5A6C447DBAC1DF296
  • Produces: []

However, when I use paging:

  • /stock/lots/depotDetailed/?depot_uuid=F9CAEB16168443C5A6C447DBAC1DF296&paging=true
  • Produces: {"pager":{"total":0,"page":1,"page_size":100,"page_min":null,"page_max":null,"page_count":0},"rows":[]}
  • Which looks like the paging part is working correctly

As far as I'm concerned, I think this is ready to go.

let filteredRows = await getLots(sql, params, clause);
if (filteredRows.length === 0) { return []; }
const filteredRows = await getLots(sql, params, clause);
let filteredRowsPaged = params.paging ? filteredRows.rows : filteredRows;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

if (rows.length === 0) {
// update page_min and page_max after the query
// in case of empty result
pager.page_min = null;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@mbayopanda
Copy link
Collaborator Author

Thank you @jmcameron

bors r+

@bors
Copy link
Contributor

bors bot commented Apr 1, 2023

Build succeeded:

@bors bors bot merged commit 10076d9 into Third-Culture-Software:master Apr 1, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants